package group_1.MultiSnake;

import java.util.ArrayList;
import java.util.List;

import org.anddev.andengine.opengl.texture.region.TextureRegion;

/**
 * Represents a players snake
 * 
 * Handle the logic determining how the snake and all its bodyparts should move.
 * The snake body is represented by BodyPart objects. Every time the snake turns
 * a new bodypart is created representing the turn and then another representing
 * the new vertical or horizontal bodypart.
 *  
 */
public class Snake implements SnakeConstants{
	
	private static final int LAST_BODY_PART = 0;
	private static final int SECOND_LAST_BODY_PART = 1;
	
	private TextureRegion headTextureRegion;		// Image representing the head
	private TextureRegion bodyTextureRegion;		// Image representing the vertical and horizontal bodyparts
	private TextureRegion bodyTurnTextureRegion;	// Image representing the bodypart when turning
	
	private ArrayList<BodyPart> bodyParts;		// List of body parts
	private Head head;							// The head of the snake
	private int currDirection;					// The current direction of the snake
	private int bodyHeadWidthDiff;				// Used to position head correct relative the body
	
	private Item item;							// Item currently hold by the snake
	
	private boolean isAlive = true;				// Determines if the snake is alive or not
	
	/**
	 * Constructor - construct a new snake object
	 * 
	 * @param startX The starting x-position of the snake
	 * @param startY The starting y-position of the snake
	 * @param startDirection The starting direction of the snake
	 */
	public Snake(int startX, int startY, int startDirection, TextureRegion headTextureRegion, TextureRegion bodyTextureRegion, TextureRegion bodyTurnTextureRegion) {
		this.currDirection = startDirection;
		this.headTextureRegion = headTextureRegion;
		this.bodyTextureRegion = bodyTextureRegion;
		this.bodyTurnTextureRegion = bodyTurnTextureRegion;
		
		bodyParts = new ArrayList<BodyPart>();
		
		bodyHeadWidthDiff = (HEAD_WIDTH-BODY_THICKNESS)/2;
		initSnake(startX, startY, startDirection);
	}
	
	/**
	 * Initialize the snake.
	 * Creates a head and its first bodypart.
	 * 
	 * @param startX The start x-direction of the snake
	 * @param startY The start y-direction of the snake
	 * @param startDirection The starting direction of the snake
	 */
	private void initSnake(float startX, float startY, int startDirection) {
		bodyParts.add(LAST_BODY_PART, new BodyPart(startX, startY, startDirection, bodyTextureRegion));
		head = new Head(0, 0, BODY_THICKNESS, BODY_THICKNESS, startDirection, headTextureRegion);
		BodyPart part = getLastBodyPart();
		
		switch(startDirection) {
		case RIGHT:
			head.setPosition(part.getFrontX(), part.getFrontY()-bodyHeadWidthDiff);
			break;
		case LEFT:
			head.setRotation(180);
			head.setPosition(part.getFrontX()-HEAD_LENGTH, part.getY()-bodyHeadWidthDiff);
			break;
		case UP:
			head.setRotation(90);
			head.setPosition(part.getFrontX()-bodyHeadWidthDiff, part.getY()-HEAD_LENGTH);
			break;
		case DOWN:
			head.setRotation(270);
			head.setPosition(part.getFrontX()-bodyHeadWidthDiff, part.getY()+HEAD_LENGTH);
			break;
		}
	}
	
	/**
	 * Update the position of the snake if it is still alive
	 */
	public void move() {
		if(isAlive) {
			BodyPart part = getLastBodyPart();
			if(part != null) {
				part.changeLength(SNAKE_SPEED);
			}
			head.move(SNAKE_SPEED);
		}
	}
	
	/**
	 * Test if a new direction is valid or not. The snake can not turn to an opposite
	 * direction from the direction that the snake is currently moving. The snake can not
	 * turn to the same direction that the snake is currently moving.
	 * 
	 * @param direction The new direction
	 * 
	 * @return True if the new direction is valid. False otherwise
	 */
	public boolean validNewDirection(int direction) {
		if(currDirection == direction)
			return false;
		
		switch(direction) {
		case UP:
			return currDirection != DOWN;
		case DOWN:
			return currDirection != UP;
		case RIGHT:
			return currDirection != LEFT;
		case LEFT:
			return currDirection != RIGHT;
		default:
			return false;
		}
	}
	
	/**
	 * Turn the snake to a new direction.
	 * Create a turn bodypart and then a new vertical or horizontal bodypart.
	 * 
	 * @param atX The x-position where the snake should change direction
	 * @param atY The y-position where the snake should change direction
	 * @param direction The new direction of the snake
	 */
	public boolean turn(float atX, float atY, int direction) {
		
		if(!validNewDirection(direction))
			return false;
		
		moveToPos(atX,atY);
		
		insertTurnBodyPart(direction);
		BodyPart part = getLastBodyPart();
		currDirection = direction;
		bodyParts.add(LAST_BODY_PART, new BodyPart(part.getFrontX(), part.getFrontY(), currDirection, bodyTextureRegion));
		
		head.setDirection(direction);
		updateHeadPosition();
		return true;
	}
	
	/**
	 * Update the position of the head of the snake.
	 */
	private void updateHeadPosition() {
		float x = getLastBodyPart().getFrontX();
		float y = getLastBodyPart().getFrontY();
		
		switch(currDirection) {
		case UP:
			head.setPosition(x-bodyHeadWidthDiff, y-HEAD_LENGTH);
			break;
		case DOWN:
			head.setPosition(x-bodyHeadWidthDiff, y);
			break;
		case RIGHT:
			head.setPosition(x, y-bodyHeadWidthDiff);
			break;
		case LEFT:
			head.setPosition(x-HEAD_LENGTH, y-bodyHeadWidthDiff);
			break;
		}
	}
	
	/**
	 * Insert a new turn bodypart.
	 * 
	 * @param newDirection The new direction of the snake
	 */
	private void insertTurnBodyPart(int newDirection) {
		float atX = 0, atY = 0;
		BodyPart part = getLastBodyPart();
		switch(currDirection) {
		case RIGHT:
		case DOWN:
			atX = part.getFrontX();
			atY = part.getFrontY();
			break;
		case LEFT:
			atX = part.getFrontX()-BODY_THICKNESS;
			atY = part.getFrontY();
			break;
		case UP:
			atX = part.getFrontX();
			atY = part.getFrontY()-BODY_THICKNESS;
			break;
		}
		
		bodyParts.add(LAST_BODY_PART, new BodyPart(atX, atY, currDirection, newDirection, bodyTurnTextureRegion));
	}
	
	/**
	 * Hit the snake at a given position.
	 * When a bodypart is hit by a bullet that part is destroyed
	 * 
	 * @param part The part that is hit
	 * @return 
	 */
	public List<BodyPart> hit(BodyPart part) {
		List<BodyPart> newParts = new ArrayList<BodyPart>();
//		BodyPart newPart = new BodyPart(part.getX(), part.getY(), part.getGrowDirection(), bodyTextureRegion);
		bodyParts.remove(part);
		return newParts;
	}
	
	/**
	 * Move the snake to a given position.
	 * Resizes the last bodypart to reposition the snake.
	 * 
	 * @param newX The x-position to move the snake to
	 * @param newY The y-position to move the snake to
	 */
	public void moveToPos(float newX, float newY) {
		BodyPart part = getLastBodyPart();
		float xDiff = part.getFrontX()-newX;
		float yDiff = part.getFrontY()-newY;
		
		switch(currDirection) {
		case UP:
			part.changeLength(yDiff);
//			head.setPosition(head.getX(), head.getY()+yDiff);
			break;
		case DOWN:
			part.changeLength(-yDiff);
//			head.setPosition(head.getX(), head.getY()-yDiff);
			break;
		case RIGHT:
			part.changeLength(-xDiff);
//			head.setPosition(head.getX()-xDiff, head.getY());
			break;
		case LEFT:
			part.changeLength(xDiff);
//			head.setPosition(head.getX()+xDiff, head.getY());
			break;
		}
	}
	
	/**
	 * Get the front x-position of the snake
	 * 
	 * @return front x-position of the snake
	 */
	public float getFrontX(){
		
		float x = getLastBodyPart().getFrontX();
		switch(currDirection) {
		case UP:
		case DOWN:
			return x;
		case LEFT:
			return x-HEAD_LENGTH;
		case RIGHT:
			return x+HEAD_LENGTH;
		default: // should not come here
			return 0;
		}
	}
	
	/**
	 * Get the front y-position of the snake.
	 * 
	 * @return front y-position of the snake
	 */
	public float getFrontY() {
		float y = getLastBodyPart().getFrontY();
		switch(currDirection) {
		case UP:
			return y-HEAD_LENGTH;
		case DOWN:
			return y+HEAD_LENGTH;
		case LEFT:
		case RIGHT:
			return y;
		default: // should not come here
			return 0;
		}
	}
	
	/**
	 * Determine if snake is alive or not
	 * 
	 * @return true if snake is alive. False otherwise
	 */
	public boolean isAlive() {
		return isAlive;
	}
	
	/**
	 * Kill the snake
	 */
	public void kill() {
		isAlive = false;
	}
	
	/**
	 * Get the current direction of the snake.
	 * 
	 * @return the direction of the snake
	 */
	public int getCurrentDirection() {
		return currDirection;
	}
	
	/**
	 * Get the head of the snake.
	 * 
	 * @return the head of the snake
	 */
	public Head getHead() {
		return this.head;
	}
	
	/**
	 * Set an item to the snake
	 * 
	 * @param item the item to give the snake
	 */
	public void setItem(Item item) {
		this.item = item;
	}
	
	/**
	 * Get the item currently hold by the snake
	 * 
	 * @return the item currently hold by the snake
	 */
	public Item getItem() {
		return item;
	}
	
	/**
	 * Use the item currently hold by the snake.
	 * 
	 * @return the item used
	 */
	public Item useItem() {
		if(item != null) {
			if(item instanceof Bullet)
				((Bullet)item).setDirection(currDirection);
			item.use();
		}
		
		Item temp = item;
		item = null;
		return temp;
	}
	
	/**
	 * Get all the bodyparts of the snake
	 * 
	 * @return a list of all the bodyparts of the snake
	 */
	public ArrayList<BodyPart> getBodyParts() {
		return this.bodyParts;
	}
	
	/**
	 * Get the last added bodypart
	 * 
	 * @return the last bodypart of the snake
	 */
	public BodyPart getLastBodyPart() {
		if(bodyParts.size() > 0)
			return this.bodyParts.get(LAST_BODY_PART);
		else
			return null;
	}
	
	/**
	 * Get the second last added bodypart
	 * 
	 * @return the second last added bodypart
	 */
	public BodyPart getSecondLastBodyPart() {
		if(bodyParts.size() > 1)
			return this.bodyParts.get(SECOND_LAST_BODY_PART);
		else
			return null;
	}
}
